﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PerCederberg.Grammatica.Runtime;

namespace SafeScript.MetaModel
{
    public class ErrorReport : List<ErrorEntry>
    {
        public void SortByLine()
        {
            this.Sort(delegate(ErrorEntry e1, ErrorEntry e2) { return e1.Line == e2.Line ? 0 : (e1.Line < e2.Line ? -1 : 1); });
        }
    }

    public enum ErrorType
    {
        UnknownNamespace = 1,
        UnknownClass = 2,
        UnknownInterface = 3,
        NotAClass = 4,
        NotAnInterface = 5,
        NotAType = 6,
        UnknownType = 7,
        DuplicateName = 8,
        CtorOutsideClass = 9,
        InheritanceLoop = 10,
        NotANamespace = 11,
        UnknownIdentifier = 12,
        MultipleNamespaceInitializationCode = 13,
        UnresolvedType = 14,
        IncompatibleTypes = 15,
        NotAReferenceType = 16,
        NotAValidInteger = 17,
        NotAValidFloat = 18,
        SuperNotAllowed = 19,
        ThisNotAllowed = 20,
        NotAnObject = 21,
        NonStaticMemberNotAccessible = 22,
        SignatureOfOverrideDoesNotMatch = 23,
        NoFunctionToOverride = 24,
        NotAFunction = 25,
        FunctionParameterCountMismatch = 26,
        FunctionParameterTypeMismatch = 27,
        DuplicateFunction = 28,
        FunctionCallIsAmbiguous = 29,
        NoMatchingFunction = 30,
        MultiplClassInheritance = 31,
        ClassInheritanceInInterface = 32,
        StatementInInterface = 33,
        VariableInInterface = 34,
        NamespaceInType = 35,
        OverridingSealedFunction = 36,
        ClassIsSealed = 37,
        InstantiationOfAbstractClass = 38,
        FunctionReturnTypeMismatch = 39,
        OverrideMissing = 40,
        AbstractFunctionInNonAbstractClass = 41,
        NoPropertyGetterToOverride = 42,
        NonAbstractFunctionInInterface = 43,
        NonAbstractPropertyInInterface = 44,
        PropertyGetterImplementationMissing = 45,
        FunctionImplementationMissing = 46,
        OverridingSealedProperty = 47,
        NeitherGetterNotSetter = 48,
        NoPropertySetterToOverride = 49,
        PropertySetterImplementationMissing = 50,
        OverridingStaticMember = 51,
        SealingStaticMember = 52,
        StaticCodeCannotAccessNonStaticMember = 53,
        SameFunctionNameSameStatic = 54,
        StaticFunctionInInterface = 55,
        StaticPropertyInInterface = 56,
        InterfaceInInterface = 57,
        ClassInInterface = 58,
        CtorInInterface = 59,
        ExpectedInt = 60,
        ConditionMustBeBool = 61,
        NotAList = 62,
        DuplicateNameInInterfaces = 63,
        ReturnTypeMismatch = 64,
        PropertyHasNoGetter = 65,
        PropertyHasNoSetter = 66,
        UnknownAttribute = 67,
        WrongUsageOfAttribute = 68,
        WrongAttributeValues = 69,
        MixedDefaultGetSetWithCustomGetSet = 70,
        NativeClassMustBeSealed = 71,
        NativeClassMustBeAbstract = 72,
        NativeClassMustNotInherit = 73,
        CannotInferType = 74,
        WrongClosingTag = 75,
        LibraryMissing = 76,
        NoDefaultConstructor = 77,
        NoStaticMemberInXml = 78,
        NoMatchingConstructor = 79,
        NotAListOrDict = 80,
        ReturnInIterator = 81,
        YieldInFunction = 82,
        NotAnIterator = 83,
        BindingNeedsPropertyOrFunction = 84,
        AbstractFunctionInNamespace = 85,
        SyntaxError = 86,
        IOError = 87,
        FileNotFound = 88,
        AbstractPropertyWithImplementation = 89,
        AbstractStaticMember = 90,
        AbstractPropertyInNonAbstractClass = 91,
        AbstractPropertyInNamespace = 92,
        TypeNotSupportedAsEnumBaseType = 93,
        OverridingOutsideClass = 94
    }

    public class ErrorEntry
    {
        public ErrorType Type;
        public string Filename;
        public int Line;
        public int Column;
        public string Info;

        public ErrorEntry(System.IO.IOException ex, string filename)
        {
            this.Filename = filename;
            this.Type = ErrorType.IOError;
            this.Line = -1;
        }

        public ErrorEntry(Node node)
        {
            this.Column = node.StartColumn;
            this.Line = node.StartLine;
            this.Filename = Compiler.CurrentCompiler.GetFilename(node);
        }

        public ErrorEntry(ParseException ex, string filename)
        {
            this.Column = ex.Column;
            this.Line = ex.Line;
            this.Info = ex.ErrorMessage;
            this.Type = ErrorType.SyntaxError;
            this.Filename = filename;
        }

        public ErrorEntry(string missingFilename)
        {
            this.Line = -1;
            this.Filename = missingFilename;
            this.Type = ErrorType.FileNotFound;
        }

        public override string ToString()
        {
            string str = this.LocationToString() + ": ";

            switch (this.Type)
            {
                case ErrorType.UnknownNamespace:
                    str += "Could not find namespace " + this.Info;
                    break;
                case ErrorType.UnknownClass:
                    str += "Could not find class " + this.Info;
                    break;
                case ErrorType.UnknownInterface:
                    str += "Could not find interface " + this.Info;
                    break;
                case ErrorType.UnknownType:
                    if (this.Info != null)
                        str += "Could not find type " + this.Info;
                    else
                        str += "Unknown type";
                    break;
                case ErrorType.NotAClass:
                    if (this.Info == null)
                        str += "Expected a class";
                    else
                        str += this.Info + " is not a class";
                    break;
                case ErrorType.NotAnInterface:
                    str += this.Info + " is not an interface";
                    break;
                case ErrorType.NotAType:
                    if (this.Info != null)
                        str += this.Info + " is not a type";
                    else
                        str += "Expected a type";
                    break;
                case ErrorType.NotANamespace:
                    str += this.Info + " is not a namespace";
                    break;
                case ErrorType.DuplicateName:
                    str += "The name " + this.Info + " has already been used";
                    break;
                case ErrorType.CtorOutsideClass:
                    str += "A constructor cannot be used outside of a class";
                    break;
                case ErrorType.InheritanceLoop:
                    str += "Class or interface " + this.Info + " inherits itself";
                    break;
                case ErrorType.UnknownIdentifier:
                    str += "Could not find identifier " + this.Info;
                    break;
                case ErrorType.MultipleNamespaceInitializationCode:
                    str += "Initialization code for class or namespace " + this.Info + "was found at more than one place. This leaves doubt to the order in which this code is to be executed and is therefore forbidden.";
                    break;
                case ErrorType.UnresolvedType:
                    if (this.Info != null)
                        str += "Could not resolve type of " + this.Info;
                    else
                        str += "Could not resolve type";
                    break;
                case ErrorType.IncompatibleTypes:
                    str += "The operand types are not compatible";
                    break;
                case ErrorType.NotAReferenceType:
                    str += "The operand cannot be assigned to";
                    break;
                case ErrorType.NotAValidInteger:
                    str += "The number " + this.Info + " is not a valid integer";
                    break;
                case ErrorType.NotAValidFloat:
                    str += "The number " + this.Info + " is not a valid float";
                    break;
                case ErrorType.SuperNotAllowed:
                    str += "The keyword 'super' is not allowed in this context";
                    break;
                case ErrorType.ThisNotAllowed:
                    str += "The keyword 'this' is not allowed in this context";
                    break;
                case ErrorType.NotAnObject:
                    str += "Expected an object";
                    break;
                case ErrorType.NonStaticMemberNotAccessible:
                    str += "The member '" + this.Info + "' is not static and therefore not accessible in this context";
                    break;
                case ErrorType.SignatureOfOverrideDoesNotMatch:
                    str += "The signature of '" + this.Info + "' does not match that of the overriden function";
                    break;
                case ErrorType.NoFunctionToOverride:
                    str += "There is no suitable function to override for '" + this.Info + "'";
                    break;
                case ErrorType.NotAFunction:
                    str += "The expression is not a function";
                    break;
                case ErrorType.FunctionParameterCountMismatch:
                    str += "The number of parameters does not match with the function signature";
                    break;
                case ErrorType.FunctionParameterTypeMismatch:
                    str += "The type of parameter " + this.Info + " does not match with the function signature";
                    break;
                case ErrorType.DuplicateFunction:
                    str += "A function with the name " + this.Info + " and the same parameters exists already";
                    break;
                case ErrorType.FunctionCallIsAmbiguous:
                    str += "The function call is ambiguous";
                    break;
                case ErrorType.NoMatchingFunction:
                    str += "No function with matching signature found";
                    break;
                case ErrorType.MultiplClassInheritance:
                    str += "The class " + this.Info + " inherits from multiple base classes. This is not allowed.";
                    break;
                case ErrorType.ClassInheritanceInInterface:
                    str += "The interface " + this.Info + " inherits from a class which is not allowed.";
                    break;
                case ErrorType.StatementInInterface:
                    str += "The interface " + this.Info + " contains a statement which is not allowed.";
                    break;
                case ErrorType.VariableInInterface:
                    str += "The interface " + this.Info + " contains a variable which is not allowed. Use a property instead.";
                    break;
                case ErrorType.NamespaceInType:
                    str += "The type " + this.Info + " contains a namespace which is not allowed.";
                    break;
                case ErrorType.OverridingSealedFunction:
                    str += "The member function " + this.Info + " is sealed and cannot be overwritten";
                    break;
                case ErrorType.ClassIsSealed:
                    str += "The class " + this.Info + " is sealed. Using this class as a base class is not allowed.";
                    break;
                case ErrorType.InstantiationOfAbstractClass:
                    str += "The class " + this.Info + " is abstract and cannot be instantiated";
                    break;
                case ErrorType.FunctionReturnTypeMismatch:
                    str += "Two functions of the name " + this.Info + " and the same parameter list exist, but their return type is different";
                    break;
                case ErrorType.OverrideMissing:
                    str += "The class member " + this.Info + " seems to override an implementation of the base class, but the keyword 'override' is missing";
                    break;
                case ErrorType.AbstractFunctionInNonAbstractClass:
                    str += "The function " + this.Info + " is abstract but the containing class is not abstract.";
                    break;
                case ErrorType.NoPropertyGetterToOverride:
                    str += "There is no suitable property getter to override for '" + this.Info + "'";
                    break;
                case ErrorType.PropertyGetterImplementationMissing:
                    str += "No getter for property " + this.Info + " has been implemented in this class";
                    break;
                case ErrorType.FunctionImplementationMissing:
                    str += "The member function " + this.Info + " is not implemented in this class";
                    break;
                case ErrorType.OverridingSealedProperty:
                    str += "The property " + this.Info + " is sealed and cannot be overwritten";
                    break;         
                case ErrorType.NeitherGetterNotSetter:
                    str += "The property " + this.Info + " features neither a getter not a setter.";
                    break;
                case ErrorType.NoPropertySetterToOverride:
                    str += "There is no suitable property setter to override for '" + this.Info + "'";
                    break;
                case ErrorType.PropertySetterImplementationMissing:
                    str += "No setter for property " + this.Info + " has been implemented in this class";
                    break;
                case ErrorType.OverridingStaticMember:
                    str += "Class member " + this.Info + " is static and therefore it cannot override a base class member";
                    break;
                case ErrorType.SealingStaticMember:
                    str += "Class member " + this.Info + " is static and therefore it cannot be sealed";
                    break;
                case ErrorType.StaticCodeCannotAccessNonStaticMember:
                    str += "Non static member " + this.Info + " cannot be accessed from a static context";
                    break;
                case ErrorType.SameFunctionNameSameStatic:
                    str += "The functions named " + this.Info + " must all be static or all non-static. Mixes are not allowed";
                    break;
                case ErrorType.StaticFunctionInInterface:
                    str += "The function named " + this.Info + " is static, but static members are not allowed in interfaces.";
                    break;
                case ErrorType.StaticPropertyInInterface:
                    str += "The property named " + this.Info + " is static, but static members are not allowed in interfaces.";
                    break;
                case ErrorType.InterfaceInInterface:
                    str += "The interface " + this.Info + " cannot be defined inside an interface.";
                    break;
                case ErrorType.ClassInInterface:
                    str += "The class " + this.Info + " cannot be defined inside an interface.";
                    break;
                case ErrorType.CtorInInterface:
                    str += "The interface " + this.Info + " cannot contain a constructor.";
                    break;
                case ErrorType.ExpectedInt:
                    str += "Expected an object of type int";
                    break;
                case ErrorType.ConditionMustBeBool:
                    str += "The condition expression must be of type bool";
                    break;
                case ErrorType.NotAList:
                    str += "Expected a list<T>";
                    break;
                case ErrorType.DuplicateNameInInterfaces:
                    str += "Two interfaces inherited by this class feature a member named " + this.Info;
                    break;
                case ErrorType.ReturnTypeMismatch:
                    str += "The type of the return statement does not match the expected return type";
                    break;
                case ErrorType.PropertyHasNoGetter:
                    str += "The property " + this.Info + " has no getter and can not be read";
                    break;
                case ErrorType.PropertyHasNoSetter:
                    str += "The property " + this.Info + " has no setter and can not be written";
                    break;
                case ErrorType.UnknownAttribute:
                    str += "The attribute " + this.Info + " is unknown.";
                    break;
                case ErrorType.WrongUsageOfAttribute:
                    str += "The attribute " + this.Info + " cannot be used in this context.";
                    break;
                case ErrorType.WrongAttributeValues:
                    str += "The values for attribute " + this.Info + " are either of the wrong type or their number does not match.";
                    break;
                case ErrorType.MixedDefaultGetSetWithCustomGetSet:
                    str += "It is not allowed to mix a default getter or setter with a custom implemented getter or setter.";
                    break;
                case ErrorType.NativeClassMustBeAbstract:
                    str += "The class " + this.Info + " must be marked as abstract because it is a native class that cannot be instantiated directly.";
                    break;
                case ErrorType.NativeClassMustBeSealed:
                    str += "The class " + this.Info + " must be marked as sealed because it is a native class that cannot be used as a base class.";
                    break;
                case ErrorType.NativeClassMustNotInherit:
                    str += "The class " + this.Info + " must not inherit from any non-native/non-html class because it is a native/html class.";
                    break;
                case ErrorType.CannotInferType:
                    str += "Cannot infer the type of this expression";
                    break;
                case ErrorType.WrongClosingTag:
                    str += "Expected another closing tag.";
                    break;
                case ErrorType.LibraryMissing:
                    str += "A library featuring the namespace " + this.Info + " is misssing";
                    break;
                case ErrorType.NoDefaultConstructor:
                    str += "The class " + this.Info + " does not feature a default constructor, i.e. a constructor that has no parameters.";
                    break;
                case ErrorType.NoStaticMemberInXml:
                    str += "The usage of the static member " + this.Info + " is not allowed in Xml";
                    break;
                case ErrorType.NoMatchingConstructor:
                    str += "There is no matching constructor";
                    break;
                case ErrorType.NotAListOrDict:
                    str += "Expected a list or dict";
                    break;
                case ErrorType.ReturnInIterator:
                    str += "The return statement is not allowed inside an iterator. Use yield return instead.";
                    break;
                case ErrorType.YieldInFunction:
                    str += "The yield statement is not allowed in non-iterator functions. Use either return or yields inside the function.";
                    break;
                case ErrorType.NotAnIterator:
                    str += "Expected an iterator, i.e. a function with a non-void return type and no parameters.";
                    break;
                case ErrorType.BindingNeedsPropertyOrFunction:
                    str += "A binding can only be established between functions and properties.";
                    break;
                case ErrorType.AbstractFunctionInNamespace:
                    str += "The function " + this.Info + " is abstract and cannot be contained in a namespace.";
                    break;
                case ErrorType.SyntaxError:
                    str += this.Info;
                    break;
                case ErrorType.IOError:
                    str += "Could not read or write file";
                    break;
                case ErrorType.FileNotFound:
                    str += "Could not locate file";
                    break;
                case ErrorType.AbstractPropertyWithImplementation:
                    str += "Abstract property must not contain an implementation for getter of setter";
                    break;
                case ErrorType.AbstractStaticMember:
                    str += "Static members must not be abstract";
                    break;
                case ErrorType.AbstractPropertyInNamespace:
                    str += "The property " + this.Info + " is abstract and cannot be contained in a namespace.";
                    break;
                case ErrorType.AbstractPropertyInNonAbstractClass:
                    str += "The property " + this.Info + " is abstract but its containing class is not abstract";
                    break;
                case ErrorType.TypeNotSupportedAsEnumBaseType:
                    str += "The type cannot be used as a base type for enums";
                    break;
                case ErrorType.OverridingOutsideClass:
                    str += "Overriding is only allowed in class members";
                    break;
            }

            return str;
        }

        private string ShortFilename
        {
            get
            {
                int pos = this.Filename.LastIndexOf('/');
                int pos2 = this.Filename.LastIndexOf('\\');
                pos = Math.Max(pos, pos2);
                return this.Filename.Substring(pos + 1);
            }
        }

        private string LocationToString()
        {
            if (this.Line < 0)
                return this.Filename + ": ";            
            return this.ShortFilename + ": Line " + this.Line.ToString() + ", Column " + this.Column.ToString();
        }
    }
}
